package net.wangds.yunz.auth;


import net.wangds.auth.module.Anonymous;
import net.wangds.yunz.auth.filter.AnonymousFilter;
import net.wangds.yunz.auth.filter.CosFilter;
import net.wangds.yunz.auth.filter.JwtAuthenticationTokenFilter;
import net.wangds.yunz.auth.filter.JwtLoginFilter;
import net.wangds.yunz.auth.handler.JwtAccessDeniedHandler;
import net.wangds.yunz.auth.handler.LoginAuthenticationFailureHandler;
import net.wangds.spring.utils.SpringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

//@Configuration
//@EnableWebSecurity
//@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ProgramWebSecurityConfig extends WebSecurityConfigurerAdapter {

    //@Value("${net.wangds.security.enableCros:false}")
    private boolean confEnableCros = false;

    @Value("${net.wangds.security.loginService:/auth/login}")
    private String confLoginService;

    @Value("${net.wangds.security.logoutService:/auth/logout}")
    private String confLogoutService;

    @Value("${net.wangds.security.maxSessionCount:1}")
    private int maxSessionCount;

    //用户登陆服务
    @Autowired
    private UserDetailsService mmSrvUserDtail;
    //验证异常
    @Autowired
    private AuthenticationEntryPoint mmCmpAuthEntryPoint;
    //权限异常
    @Autowired
    private AccessDeniedHandler mmAccessDeniedHandler;
    //登陆成功处理
    @Autowired
    private AuthenticationSuccessHandler mmOnAuthenticationSuccess;
    //登陆失败处理
    @Autowired
    private AuthenticationFailureHandler mmOnAuthenticationFailure;
    //登出处理
    @Autowired
    private LogoutSuccessHandler mmOnLogoutSuccess;
    //Session过期策略
    @Autowired
    private SessionInformationExpiredStrategy mmSessionExpiredStrategy;

    private static final String[] TEXT_YES = new String[]{"true", "t", "yes", "y", "ok", "是", "1"};

    @Bean
    public BCryptPasswordEncoder bcCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean(name = BeanIds.AUTHENTICATION_MANAGER)//解决AuthenticationManager不能注入的问题
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(mmSrvUserDtail).passwordEncoder(bcCryptPasswordEncoder());
    }

    /**
     * 配置spring security的控制逻辑
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        if(maxSessionCount>0) {
            //设置session
            http.sessionManagement()
                    .maximumSessions(maxSessionCount)
                    .expiredSessionStrategy(mmSessionExpiredStrategy);
        }
        // 关闭跨站请求防护
        HttpSecurity sec = http.csrf().disable();

        if(confEnableCros){
            sec =  sec.cors().and();
        }

        //对请求进行认证
        //url认证配置顺序为：
        // 1.先配置放行不需要认证的 permitAll()
        // 2.然后配置 需要特定权限的 hasRole()
        // 3.最后配置 anyRequest().authenticated()
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authReg = sec.authorizeRequests();

        //是否支持跨域时的OPTIONS请求.
        if(isAllowALlOptionRequst()) {
            authReg.antMatchers(HttpMethod.OPTIONS, "/**").permitAll();
        }

        permitAllUries().forEach(c->authReg.antMatchers(c).permitAll());

        authReg.anyRequest().authenticated()
                .and()
                .exceptionHandling()
                .accessDeniedHandler(new JwtAccessDeniedHandler())
                .authenticationEntryPoint(mmCmpAuthEntryPoint)
                .and().formLogin() // 配置登录
                .loginProcessingUrl(confLoginService)//登录url
                .successHandler(mmOnAuthenticationSuccess)
                .failureHandler(mmOnAuthenticationFailure)
                .and()//登出
                .logout()
                .logoutUrl(confLogoutService)
                .logoutSuccessHandler(mmOnLogoutSuccess)
                .permitAll()
                .invalidateHttpSession(false)
                .and()
                .addFilterBefore(new CosFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilterAt(
                        SpringUtils.getBean(JwtLoginFilter.class),
                        UsernamePasswordAuthenticationFilter.class)

                .addFilterAt(new AnonymousFilter(
                                "ANONYMOUS", new Anonymous(),
                                new Anonymous().getAuthorities().stream().map(c->(GrantedAuthority)c).collect(Collectors.toList())),
                        AnonymousAuthenticationFilter.class)
                .addFilter(SpringUtils.getBean(JwtAuthenticationTokenFilter.class))
        ;

        //禁用缓存
        http.headers().cacheControl();
    }

    private boolean isAllowALlOptionRequst(){
        return StringUtils.equalsAnyIgnoreCase(SpringUtils.getSysEnvOrSpringEvnOption("OPT_SEC_WEB_ALLOW_ALL_OPTIONS_REQ", "y"), TEXT_YES);
    }

    private List<String> permitAllUries(){
        String str = SpringUtils.getSysEnvOrSpringEvnOption("OPT_SEC_WEB_PERMIT_ALL", "/demo/**,/auth/initialize/**,/auth/account/file/**,/auth/account/uploadForm,/**/test/**,/swagger**,/v2/api-docs,/swagger-resources/configuration/ui,/swagger-resources/configuration/security,/webjars/**,/auth/reg,/auth/verifycode");
        String[] opts = StringUtils.split(str, ',');
        return Arrays.stream(opts).filter(StringUtils::isNotBlank).collect(Collectors.toList());
    }

}
